﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace GwLib.UnitTesting
{
    public class ObjectStore<T> : IObjectStore<T> where T : class, new()
    {
        private static object _listLock = new object();
        private ConcurrentDictionary<ObjectStoreTriggerType, List<Action<T, List<T>>>> _triggers = new ConcurrentDictionary<ObjectStoreTriggerType, List<Action<T, List<T>>>>();
        private List<T> _records = new List<T>();
        private Func<object, object> _copyDelegate;

        public ObjectStore()
        {
            _triggers[ObjectStoreTriggerType.Insert] = new List<Action<T, List<T>>>();
            _triggers[ObjectStoreTriggerType.Update] = new List<Action<T, List<T>>>();
            _triggers[ObjectStoreTriggerType.Delete] = new List<Action<T, List<T>>>(); 
        }

        public void AddTrigger(ObjectStoreTriggerType triggerType, Action<T, List<T>> trigger)
        {
            _triggers[triggerType].Add(trigger);
        }

        public void SetCopyImplementation(Func<object, object> copyDelegate)
        {
            _copyDelegate = copyDelegate;
        }

        public void Reset() 
        {
            lock (_listLock)
            {
                _records.Clear();
            }
        }
        
        public int Count
        {
            get
            {
                return _records.Count;
            }
        }

        public T Insert(T obj)
        {
            var copy = ApplyTriggers(ObjectStoreTriggerType.Insert, Copy(obj));

            lock (_listLock) 
            {
                _records.Add(copy);
            }
            return copy;
        }

        public T UpdateWhere(T obj, Func<T, bool> where)
        {
            int rowsAffected = 0;
            var copy = ApplyTriggers(ObjectStoreTriggerType.Update, Copy(obj));
            var subset = _records.Where(where).ToList();
            rowsAffected = subset.Count;
            lock (_listLock)
            {
                foreach (var recordToRemove in subset) _records.Remove(recordToRemove);
                _records.Add(copy);
            }

            return copy;
        }

        public T UpdateByColumn(T obj, Expression<Func<T, object>> columnExpression)
        {
            var copy = ApplyTriggers(ObjectStoreTriggerType.Update, Copy(obj));
            int rowsAffected = DeleteByColumn(obj, columnExpression);
            lock (_listLock)
            {
                _records.Add(copy);
            }

            return copy;
        }

        public int DeleteWhere(Func<T, bool> where)
        {
            int rowsAffected = 0;
            var subset = _records.Where(where).ToList();
            rowsAffected = subset.Count;

            foreach (var recordToDelete in subset)
            {
                ApplyTriggers(ObjectStoreTriggerType.Delete, recordToDelete);
                lock (_listLock)
                {
                    _records.Remove(recordToDelete);
                }
            }

            return rowsAffected;
        }

        public int DeleteByColumn(T obj, Expression<Func<T, object>> columnExpression)
        {
            int rowsAffected = 0;
            var subset = GetFilteredList(obj, columnExpression);
            rowsAffected = (subset == null) ? 0 : subset.Count;

            foreach (var recordToDelete in subset)
            {
                ApplyTriggers(ObjectStoreTriggerType.Delete, recordToDelete);
                lock (_listLock)
                {
                    _records.Remove(recordToDelete);
                }
            }

            return rowsAffected;
        }

        public List<T> SelectList(Func<T, bool> where = null)
        {
            var subset = (where == null) ? _records : _records.Where(where).ToList();
            return Copy(subset);
        }

        public T SelectOne(Func<T, bool> where = null)
        {
            return SelectList(where).FirstOrDefault();
        }

        private TC Copy<TC>(TC obj) where TC : class, new()
        {
            TC copy = obj;
            if (_copyDelegate != null) copy = _copyDelegate(obj) as TC;
            return copy;
        }

        private T ApplyTriggers(ObjectStoreTriggerType triggerType, T obj)
        {
            foreach (var trigger in _triggers[triggerType])
            {
                trigger(obj, _records);
            }
            return obj;
        }
        
        private List<T> GetFilteredList(T obj, Expression<Func<T, object>> columnExpression)
        {
            List<T> subset = null;

            var exprBody = columnExpression.Body;
            PropertyInfo prop = null;
            if (exprBody is MemberExpression)
            {
                prop = (exprBody as MemberExpression).Member as PropertyInfo;
            }
            else if (exprBody is UnaryExpression)
            {
                var unaryExp = (exprBody as UnaryExpression);
                prop = (unaryExp.Operand as MemberExpression).Member as PropertyInfo;
            }

            if (prop != null)
            {
                var gm = prop.GetGetMethod();
                var v1 = gm.Invoke(obj, null);
                subset = _records.Where((o) =>
                {
                    var v2 = gm.Invoke(o, null);
                    return (v1 == null && v1 == v2) || (v1.Equals(v2));
                }).ToList();
            }
            return subset ?? new List<T>();
        }
    }
}
